package com.unc.allergyex;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;

import org.apache.lucene.search.spell.PlainTextDictionary;
import org.apache.lucene.search.spell.SpellChecker;
import org.apache.lucene.store.RAMDirectory;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.EditText;

import com.google.gson.Gson;
import com.unc.allergyex.helpers.ObjManager;
import com.unc.allergyex.objects.AllergyPreference;
import com.unc.allergyex.objects.db.Allergen;
import com.unc.allergyex.objects.db.Restaurant;
import com.unc.allergyex.objects.tasks.MenuQueryTask;
import com.unc.allergyex.objects.tasks.UpdateTask;

/**
 * Main class of the app that is created when the app is launched.
 * This class is responsible for performing text searches for restaurants and for routing to the {@link MapSearch}, {@link Preferences}, and {@link About} screens.
 * The Gson library is used whenever there is a need to deserialize from the JSON strings used as the format of objects stored in Android Preferences files.
 * The spell-checker in the lucene library is used to perform spell-check functions during a text search of restaurant by name.
 * 
 * @author Ryan Miller
 *
 * @see com.google.gson.Gson
 *
 * @see org.apache.lucene
 */

public class AllergyEX extends Activity {

	public static final String VERSION_NUMBERS_PREFS = "versionNumbersPrefs";//Prefs file that maps versioned object string to version string
	public static final String JSON_STRINGS_PREFS = "jsonStringsPrefs";//Prefs file that maps versioned object string to Json representation of that object
	public static final String ALLERGY_PREFS = "allergyPrefs";//Prefs file that maps allergen id to boolean for allergic
	public static final String RESTAURANT_DICTIONARY = "restaurantDictionary";//Internal storage file for restaurant names
	
	//these MUST correspond to the name of the versioned objects on the server
	public static final String ALLERGEN_LIST_KEY = "allergens";//Key of allergen list versioned object
	public static final String RESTAURANT_LIST_KEY = "restaurants";//Key of restaurant list versioned object
	public static final String UNACKNOWLEDGED_ALLERGEN_UPDATE = "unacknowledged";
	
	private UpdateTask updateTask;
	private MenuQueryTask menuQueryTask;

	/**
	 * Standard function called on the launch of all Android Activities.
	 * Only performs the functions of onCreate in the Activity superclass and the function of rendering the Activity as defined by the layouts/main.xml file.
	 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}
	
	/**
	 * Standard function called whenever the Activity gains focus, including during the launch of the Activity.
	 * Responsible for requesting an update from the server and loading data stored in files into the application state.
	 * The {@link UpdateTask} executed in this method to update versioned information also loads application state from local storage, 
	 * but because this task typically runs asynchronously in its own thread, the load operations must also be performed here.
	 */
	
	@Override
	public void onResume(){
		super.onResume();
		updateTask = new UpdateTask();
		updateTask.setContext(this);
		updateTask.execute(this);
		
		//keylistener for the autocompletetextview makes enter keypresses perform the search
		EditText input = (EditText) findViewById(R.id.searchbar);
		input.setOnKeyListener(new View.OnKeyListener() {
	        public boolean onKey(View v, int keyCode, KeyEvent event) {
	        	//if it was enter and it was a keyup, go for the query
	            if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction()==0) {
	                handleQuery(v);
	                return true;
	            }
	            return false;
	        }
	    });
		
		loadDataFromLocalStorage();
		
		setUpAutoComplete();
		
		checkForUnacknowledgedAllergenUpdate();
	}
	
	/**
	 * Requests the list of all restaurants supported by the app and stored by {@link ObjManager} in the application context {@link Application} 
	 * and stores the names of all restaurants in an ArrayList used to populate the auto-complete list in the search by restaurant name box.
	 */
	
	public void setUpAutoComplete(){
		ArrayList<String> restaurantsList = new ArrayList<String>();
		String[] restaurants = new String[ObjManager.getRestaurantList().length];
		
		for(Restaurant r: ObjManager.getRestaurantList()){
			restaurantsList.add(r.getName());
		}
		restaurantsList.toArray(restaurants);

		ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.autocompleteitem, restaurants);
		AutoCompleteTextView tv = (AutoCompleteTextView)findViewById(R.id.searchbar);
		tv.setAdapter(adapter);
	}
	
	/**
	 * Launches the {@link Preferences} Activity.
	 * Called when the Preferences button in the view is clicked.
	 * 
	 * @param v the button that called this method upon being clicked
	 */

	public void launchPreferences(View v){
		Intent intent = new Intent(this, Preferences.class);
		startActivity(intent);
	}
	
	/**
	 * Launches the {@link About} Activity.
	 * Called when the About button in the view is clicked.
	 * 
	 * @param v the button that called this method upon being clicked
	 */
	
	public void launchAbout(View v){
		Intent intent = new Intent(this, About.class);
		startActivity(intent);
	}
	
	/**
	 * Launches the {@link MapSearch} Activity.
	 * Called when the Find Nearby Restaurants button in the view is clicked.
	 * 
	 * @param v the button that called this method upon being clicked
	 */
	
	public void launchMap(View v){
		ObjManager.setMapSearchRestaurantList(ObjManager.getRestaurantList());//set mapSearch restaurants to all restaurants
		Intent intent = new Intent(this, MapSearch.class);
		startActivity(intent);
	}
	
	/**
	 * Prepares the restaurant name arguments for the {@link Disambiguation} page or to go directly to the {@link Results} page for an unambiguous search.
	 * This method reads the text input in the restaurant name search box and compares this String to the list of all restaurant names stored by {@link ObjManager}.
	 * The number of matches resulting from this comparison is considered in three case:
	 * if there are zero name matches, a set of suggested restaurant names is generated by the lucene spell-checker and sent to the Disambiguation screen;
	 * if there are multiple matches, the set of all matches is sent to the Dismabiguation page;
	 * if there is only one match, the query for restaurant menu by the id of the single match is performed here in a blocking {@link MenuQueryTask} with the results sent to the Results screen.
	 * 
	 * 
	 * @param v the button that called this method upon being clicked
	 */
	
	public void handleQuery(View v){
		final AutoCompleteTextView tv = (AutoCompleteTextView) findViewById(R.id.searchbar);
		String restaurantName = tv.getText().toString();
		if (restaurantName.length() == 0){
			return;
		}
		Restaurant[] restaurantsList = ObjManager.getRestaurantList();
		Restaurant[] matchArray= new Restaurant[restaurantsList.length];
		int matchCount = 0;
		for(Restaurant r : restaurantsList){
			if(r.getName().equals(restaurantName)){
				matchArray[matchCount] = r;
				matchCount++;
			}
		}
		
		if(matchCount == 0){//if no matches, set spellchecker suggestions to ambiguous restaurant set
			try {
				SpellChecker sp = new SpellChecker(new RAMDirectory());
				sp.indexDictionary(new PlainTextDictionary(openFileInput(RESTAURANT_DICTIONARY)));
				String[] sugs = sp.suggestSimilar(restaurantName, 10);
				ArrayList<String> suggestedNames = new ArrayList<String>(Arrays.asList(sugs));//list of suggested names from spelllchecker
				Restaurant[] restaurantList = ObjManager.getRestaurantList();//stored list of all restaurants
				ArrayList<Restaurant> ambiguousRestaurantList = new ArrayList<Restaurant>();//to-be-created list of ambiguous restaurants
				for(Restaurant r : restaurantList){
					if(suggestedNames.contains(r.getName())){
						ambiguousRestaurantList.add(r);
					}
				}
				ObjManager.setAmbiguousRestaurantList(ambiguousRestaurantList.toArray(new Restaurant[ambiguousRestaurantList.size()]));
				Intent intent = new Intent(this, Disambiguation.class);
				startActivity(intent);
			} catch (IOException e) {
			}
		}else if(matchCount > 1){//if multiple matches, set all of these matches to set of ambiguous restaurants
			Restaurant[] candidates = new Restaurant[matchCount];
			
			for (int i = 0; i < matchCount; i++){
				candidates[i] = matchArray[i];
			}
			ObjManager.setAmbiguousRestaurantList(candidates);
			Intent intent = new Intent(this, Disambiguation.class);
			startActivity(intent);
		}else{//matchCount == 1, indicating unambiguous match
			menuQueryTask = new MenuQueryTask();
			menuQueryTask.setContext(this);
			menuQueryTask.execute(matchArray[0].getId());
		}
	}
	
	/**
	 * Loads data from locally stored Android Preferences files into the application state maintained in {@link Application} and referenced by {@link ObjManager}.
	 * Specifically, this method loads the list of all supported restaurants, all supported allergens, and the user's allergy preferences into application state.
	 */
	public void loadDataFromLocalStorage(){
		SharedPreferences storedJsonStrings = this.getSharedPreferences(JSON_STRINGS_PREFS, 0);
		String allergensJsonString = storedJsonStrings.getString(ALLERGEN_LIST_KEY, null);
		ObjManager.setAllergenList(new Gson().fromJson(allergensJsonString, Allergen[].class));
		String restaurantsJsonString = storedJsonStrings.getString(RESTAURANT_LIST_KEY, null);
		ObjManager.setRestaurantList(new Gson().fromJson(restaurantsJsonString, Restaurant[].class));
		
		SharedPreferences allergyPrefs = this.getSharedPreferences(ALLERGY_PREFS, 0);
		Allergen[] allergens = ObjManager.getAllergenList();
		if (allergens == null){
			allergens = new Allergen[0];
		}
		ArrayList<AllergyPreference> newAllergyPreferenceList = new ArrayList<AllergyPreference>();
		for(Allergen a : allergens){
			newAllergyPreferenceList.add(new AllergyPreference(a, allergyPrefs.getBoolean(a.getId(), false)));
		}
		ObjManager.setAllergyPreferenceList(newAllergyPreferenceList);
	}
	
	/**
	 * Check the flag to see if we have updated allergens without forcing the user to acknowledge it.  
	 * The flag lives in the version numbers file.
	 * If the flag is true, force the user to acknoledge the update. 
	 */
	public void checkForUnacknowledgedAllergenUpdate(){
		SharedPreferences versions = this.getSharedPreferences(VERSION_NUMBERS_PREFS, 0);
		Editor edit = versions.edit();
		boolean unacknowledged = versions.getBoolean(UNACKNOWLEDGED_ALLERGEN_UPDATE, false);
		if (unacknowledged){
			AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setMessage(UpdateTask.ALLERGENS_CHANGED_MESSAGE);
			builder.setCancelable(false);
			builder.setPositiveButton("Okay", null);
			builder.show();
		}
		edit.putBoolean(UNACKNOWLEDGED_ALLERGEN_UPDATE, false);
		edit.commit();
	}
	
	/**cancel the asyntasks when the activity is paused 
	 * @see android.app.Activity#onPause()
	 */
	@Override
	public void onPause(){
		super.onPause();
		cancelUpdateTask();
		cancelMenuTask();
	}
	
	/**cancel the asynctasks when the activity is destroyed
	 * @see android.app.Activity#onDestroy()
	 */
	@Override
	public void onDestroy(){
		super.onDestroy();
		cancelMenuTask();
	}
	
	
	/**cancel the menu query task if it is not null
	 */
	private void cancelMenuTask(){
		if(menuQueryTask != null){
			menuQueryTask.cancel(false);
		}
	}
	
	/**cancel the update task if it is not null
	 */
	private void cancelUpdateTask(){
		if(updateTask != null){
			updateTask.cancel(false);
		}
	}
	
}